package org.hsc.proxy;
/*
 * HttpProxyServer.java
 *
 * Created on December 22, 2007
 *
 * Name:        Moty Michaely   |   Alon Halfon
 * ID:          043413087       |   300545969
 */

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An host handler. Handles Http Requests for the specified host, with pipelining support.
 * 
 * @author Moty Michaely
 */
public class HttpProxyHostHandler {
	private static final Logger logger=LoggerFactory.getLogger(HttpProxyHostHandler.class);
    /**
     * Responses consumer.
     */
    private class ResponsesConsumer implements Runnable {
        public void run() {
            try{
                receive(responseEntries.take()); 
            } catch (InterruptedException ex) {
            	logger.error("Consumer " + host + " has done it's job...",ex);
            }  
        }
        
        /**
         * Routes the specified request
         * 
         * @param request   The request to route
         */
        private void receive(HandlerEntry entry) {
            HttpResponseCallback callback = entry.getCallback();
            HttpContext context = entry.getContext();
            try {
            	InputStream in = HttpProxyHostHandler.this.socket.getInputStream();
                // Get the response
                HttpResponse response = new HttpResponse(in);
                context.setResponse(response);
                // Execute callback function
                callback.run(context);
            }
            catch (Exception ex) {
            	logger.error(ex.getMessage(),ex);
            }
        }
    }
    
    /*
     * Requests consumer.
     */
    private class RequestsConsumer implements Runnable {
        public void run() {
            try{
                while(true) { send(requestEntries.take()); }
            } catch (InterruptedException ex) {
                System.out.println("Consumer " + host + " has done it's job...");
            } 
        }
        
        /**
         * Routes the specified request
         * 
         * @param request   The request to route
         */
        private void send(HandlerEntry entry) {
            HttpContext context = entry.getContext();
            HttpRequest request = context.getRequest();
            Cache cache = proxyServer.getCache();
            // Return if there's nothing to be processed.
            if( request == null )
                return;
            // Check if requests are cahched. If so, check if the request is cached.
            if (cache.isActive()){
                CacheComponent cacheData = null;
                // Add if-modified-since
                cacheData = cache.getCachedComponent(request.getCompleteUrl());
                // Request is cached, send an if-last-modified request
                if (cacheData != null) 
                    request.setIfModifiedSince(cacheData.getLastModified());
            }
            try {
                // Forward the request to the following server
                request.write(HttpProxyHostHandler.this.socket.getOutputStream());
            } catch (Exception ioex) {
                System.out.println("Requests Consumer " + host + " IO Exception: " + ioex.getMessage());
            }
        }
    }
    
    /*
     * Holds the host server address.
     */
    private String host;
    
    /*
     * Holds the host server port.
     */
    private int port;
    
    /*
     * Holds the socket of the server.
     */
    private volatile Socket socket;
    
    /*
     * Holds the requests to be processed
     */
    private volatile BlockingQueue<HandlerEntry> requestEntries;
    
    /*
     * Holds the requests to be processed
     */
    private volatile BlockingQueue<HandlerEntry> responseEntries;
    
    /*
     * Holds the proxy server this client is being handled by.
     */
    private HttpProxyServer proxyServer=null; 
    
    /*
     * Holds consumer thread.
     */
    private Thread requestsConsumer;
    
    /*
     * Holds consumer thread.
     */
    private Thread responsesConsumer;
           
    /*
     * Constructs a new HttpProxyHostHandler that connected to the specified host and port;
     */
    public HttpProxyHostHandler(HttpProxyServer proxyServer, String host, int port) throws UnknownHostException, IOException {
        this.requestEntries = new LinkedBlockingQueue<HandlerEntry>();
        this.responseEntries = new LinkedBlockingQueue<HandlerEntry>();
        this.proxyServer = proxyServer;
        this.host = host;
        this.port = port;
        
        logger.debug("request:"+host+":"+port);
        
        this.connect();
        this.startConsumers();
    }
    
    /*
     * Queues the specified request to be sent to the host.
     */
    public void queue(HandlerEntry entry) {
        try {
            // add the request to requests queue
            this.requestEntries.put(entry);
            // add the response callback to response callbacks queue
            this.responseEntries.put(entry);
        } catch (InterruptedException ex) {
        	logger.error(ex.getMessage(),ex);
        }
    }
    
    /*
     * Starts processing requests.
     */
    private void connect() throws UnknownHostException, IOException {
        this.socket = new Socket(host, port);
        this.socket.setSoTimeout(this.proxyServer.getTtl());
        this.socket.setTcpNoDelay(true);
    }
    
    /*
     * Disconnect from the host and stop consuming requests. 
     */
    public void disconnect() throws IOException {
        this.stopConsumers();
        if( this.socket != null && !this.socket.isClosed() )
            this.socket.close();
        this.socket = null;
        
    }
    
    /*
     * Starts processing requests.
     */
    private void startConsumers() {
        this.requestsConsumer = new Thread(new RequestsConsumer(), "Requests Consumer");
        this.requestsConsumer.start();
        this.responsesConsumer = new Thread(new ResponsesConsumer(), "Responses Consumer");
        this.responsesConsumer.start();
    }
    
    /*
     * Stops processing requests.
     */
    private void stopConsumers() {
        try {
            this.requestsConsumer.interrupt();
            this.responsesConsumer.interrupt();
            
        }catch (SecurityException ex) {
        	logger.error(ex.getMessage(),ex);
        }
    }
    
    /*
     * Stops handling this host requests.
     */
    public void stop() {
        this.stopConsumers();
        try {
            this.disconnect();
        } catch (IOException ioe) {}
    }
    
    /*
     * Gets the HttpProxyServer associated with this router
     */
    public HttpProxyServer getProxyServer() {
        return this.proxyServer;
    }
}
